/**
 * 交叉编译注释中的javascript代码块和html块，并渲染dom
 * @param {Object} model 数据
 * @param {Boolean} openLog 是否在控制台打印日志，默认为false
 * @returns HTMLElement
 */
 HTMLElement.prototype._wrap = function (model, openLog) {
    let begin = new Date();
    if (!this.innerHTML.trim()) {
        return this;
    }                                
    /********************第一步：预编译，将属性风格的js代码块预编译成html注释风格的js代码块**************
        示例：
        <div wrapper="for(let i=0;i<this.length;i++){render()}>
            内容：${this[i].content}
        </div>
        1：示例中的render()是一个假函数，实际意义是一个占位符，用来切割javascript
        2：示例中的${this[i].content}将被编译成一个模板字符串，其中的this指向model

        预编译步骤：
        1：找到包含wrapper属性的子节点，取出其中的javascript代码
        2：用"render()"这个字符串将javascript代码切割成两块
        3：将这两块javascript代码分别用html注释<!--##-->包裹起来
        4：将第一块注释插入到wrapper属性所在节点的开始标记的前面
        5：将第二块注释插入到wrapper属性所在节点的结束标记的后面

        预编译结果示例：
        <!--#for(let i=0;i<this.length;i++){#-->
            <div>内容：${this[i].content}</div>
        <!--#}#-->
    */
    let notesNodes = this.querySelectorAll("[wrapper]");
    let regRender = /\brender\s*\(\s*\)\s*;?/;  //render();的正则
    let regBrackets = /{\s*}/;                  //括号的正则
    for(let i=0;i<notesNodes.length;i++){
        let element = notesNodes[i];
        let wrapperValue = element.getAttribute("wrapper");
        if(!regRender.test(wrapperValue)){
            //语法糖处理
            if(regBrackets.test(wrapperValue)){
                //如果有一对空的大括号，则将其替换成{render();}
                //使"for(let item of this){}"等效于"for(let item of this){render();}"
                wrapperValue = wrapperValue.replace(regBrackets,"{render();}");
            }else{
                //如果没有一对空的大括号，则直接在末尾处添加{render();}
                //使"for(let item of this)"等效于"for(let item of this){render();}"
                wrapperValue = wrapperValue+"{render();}";
            }
        }
        let javascriptCodes = wrapperValue.split(regRender);
        let beginCode = javascriptCodes[0];
        let endCode = javascriptCodes[1];
        element.insertAdjacentHTML("beforebegin", `<!--#${beginCode}#-->`);
        element.insertAdjacentHTML("afterend", `<!--#${endCode}#-->`);
        element.removeAttribute("wrapper");
    }

    /******************************第二步：交叉编译****************************/
    let innerHTML = this.innerHTML;
    //将两块毗邻的javascript代码块合并
    innerHTML=innerHTML.replace(/#\s*-->\s*<!--\s*#/g,""); 
    //开始拼接html
    let dynamicCode="let _html=`"+innerHTML+"`;";  
    //遇到注释的开始标记了，表示上一块html已经拼凑完了，用 `; 结束                  
    dynamicCode = dynamicCode.replace(/<!--\s*#/g,"`;\n");  
    //遇到注释的结束标记了，表示其下是一块html代码，用 _html+=` 开始拼凑下一块html    
    dynamicCode = dynamicCode.replace(/#\s*-->/g,"\n_html+=`");
    //将拼凑好的html代码返回
    dynamicCode = dynamicCode+"return _html;";
    //创建动态函数
    const __fun__ = new Function( dynamicCode);
    if (openLog) {
        console.log("-----编译结果-----");
        console.log(__fun__);
    }
    //调用call(model),使得模板代码中的this指向model
    innerHTML = __fun__.call(model);

    /********************第三步：渲染，将html构建成dom树********************/
    this.innerHTML = innerHTML;
    if (openLog) {
        console.log("-----渲染结果-----\n" + this.outerHTML);
    }
    console.log(new Date()-begin)
    return this;
};